#include "concurrencia/MemoriaCompartida.h"
#include "Utils.h"
#include "concurrencia/Fifo.h"
#include "Estacionamiento.h"
#include "concurrencia/Semaforo.h"
#include "concurrencia/SimulacionHandler.h"
#include "concurrencia/SignalHandler.h"
#include "Auto.h"
#include "Entrada.h"
#include "Simulacion.h"
#include "Salida.h"
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include "string.h"
#include <sys/wait.h>

using namespace std;


//Inicializa cada una de las 3 entradas.
void crearEntrada(int numero, Semaforo * lecturaEntrada,
		Semaforo* escrituraEntrada, Semaforo* terminar,
		Estacionamiento * estacionamiento);

//Inicializa cada una de las 2 salidas.
void crearSalida(int numero, Semaforo * lecturaSalida,
		Semaforo* escrituraSalida);


//Crea e inicializa al administrador.
void crearProcesoAdministrador(Semaforo& semEmpezarSimulacion,
		Semaforo& semEscrituraEntrada, Semaforo& semLecturaEntrada,
		Estacionamiento& estacionamiento, Semaforo& semEscrituraSalida,
		Semaforo& semLecturaSalida, LockFile & lockTerminoSimulacion,
		MemoriaCompartida<bool> & terminoSimulacion);


//Crea los procesos para cada una de las entradas y salidas.
void crearEntradasYSalidas(Semaforo& semEmpezarSimulacion,
		Semaforo& semLecturaEntrada, Semaforo& semEscrituraEntrada,
		Semaforo& semTerminarSimulacion, Estacionamiento* estacionamiento,
		Semaforo& semLecturaSalida, Semaforo& semEscrituraSalida,
		Semaforo& semProcIdEscritos, Semaforo& escribirProcIds,
		MemoriaCompartida<int> * procIdsMemory);

int main(int argc, char* argv[]) {

	if (argc != 4) {
		cout << "Cantidad incorrecta de parámetros para la simulación." << endl
				<< "Uso: " << endl
				<< "	TpEstacionamiento [cantidad lugares] [precio/hora] [tiempo simulacion]"
				<< endl;
		exit(0);
	}
	int cantidadLugares = atoi(argv[1]);
	double precioPorHora = atoi(argv[2]);
	int tiempoEspera = atoi(argv[3]);

	Fifo salida(Utils::NOMBRE_ARCHIVO_SALIDA);

	Semaforo semEscribirProcId(Utils::NOMBRE_ARCHIVO_SEMAFORO_LECTURA_ENTRADA,
			1, 'J');
	//perror("Semaforo solicitar informacion");
	MemoriaCompartida<int> procIds;
	procIds.crear(Utils::NOMBRE_ARCHIVO_PARA_CONTADOR_DE_AUTOS_MC, 'Q', 6);

	MemoriaCompartida<bool> terminoSimulacion;
	terminoSimulacion.crear(Utils::NOMBRE_ARCHIVO_PARA_CONTADOR_DE_AUTOS_MC,
			'U');
	terminoSimulacion.escribir(false);

	LockFile lockTerminoSimulacion(
			Utils::NOMBRE_ARCHIVO_PARA_LOCK_TERMINO_SIMULACION);

	Estacionamiento estacionamiento(cantidadLugares, precioPorHora);

	Semaforo semLecturaEntrada(Utils::NOMBRE_ARCHIVO_SEMAFORO_LECTURA_ENTRADA,
			0, 'Z');
	//perror("Semaforo lectura entrada");

	Semaforo semEscrituraEntrada(
			Utils::NOMBRE_ARCHIVO_SEMAFORO_ESCRITURA_ENTRADA, 1, 'A');
	//perror("Semaforo escritura entrada");

	Semaforo semLecturaSalida(Utils::NOMBRE_ARCHIVO_SEMAFORO_LECTURA_SALIDA, 0,
			'Z');
	//perror("Semaforo lectura salida");

	Semaforo semEscrituraSalida(Utils::NOMBRE_ARCHIVO_SEMAFORO_ESCRITURA_SALIDA,
			1, 'A');
	//perror("Semaforo escritura salida");

	Semaforo semEmpezarSimulacion(
			Utils::NOMBRE_ARCHIVO_SEMAFORO_EMPEZAR_SIMULACION, 0, 'X');
	//perror("Semaforo empezar simulacion");

	Semaforo semTerminarSimulacion(
			Utils::NOMBRE_ARCHIVO_SEMAFORO_TERMINAR_SIMULACION, 0, 'Y');
	//perror("Semaforo terminar simulacion");

	Semaforo semProcIdEscritos(
			Utils::NOMBRE_ARCHIVO_SEMAFORO_TERMINAR_SIMULACION, 0, 'G');
	//perror("Semaforo solicitar informacion");

	pid_t procId = fork();

	if (procId == 0) {

		procId = fork();
		if (!procId) {
			crearEntradasYSalidas(semEmpezarSimulacion, semLecturaEntrada,
					semEscrituraEntrada, semTerminarSimulacion,
					&estacionamiento, semLecturaSalida, semEscrituraSalida,
					semProcIdEscritos, semEscribirProcId, &procIds);
			terminoSimulacion.liberar();
		} else {
			Simulacion simulacion(&semLecturaEntrada, &semEscrituraEntrada,
					&semEmpezarSimulacion, &semTerminarSimulacion,
					&semLecturaSalida, &semEscrituraSalida);
			semEscribirProcId.p();
			int pid = getpid();
			procIds.escribir(3, pid);
			semEscribirProcId.v();
			procIds.liberar();
			semProcIdEscritos.v();
			simulacion.empezar();
			estacionamiento.liberarMemoria();
			lockTerminoSimulacion.tomarLock(0);
			terminoSimulacion.escribir(true);
			lockTerminoSimulacion.liberarLock(0);
			terminoSimulacion.liberar();
		}
	} else {
		procId = fork();
		if (procId == 0) {
			crearProcesoAdministrador(semEmpezarSimulacion, semEscrituraEntrada,
					semLecturaEntrada, estacionamiento, semEscrituraSalida,
					semLecturaSalida, lockTerminoSimulacion, terminoSimulacion);
			terminoSimulacion.liberar();

		} else {
			//Esperamos a que se corran todos los procesos para obtener los pids
			semProcIdEscritos.p();
			semProcIdEscritos.p();
			semProcIdEscritos.p();
			semProcIdEscritos.p();
			semProcIdEscritos.p();
			semProcIdEscritos.p();
			int entrada1 = *procIds.leer(0);
			int entrada2 = *procIds.leer(1);
			int entrada3 = *procIds.leer(2);
			int simulacion = *procIds.leer(3);
			int salida1 = *procIds.leer(4);
			int salida2 = *procIds.leer(5);

			//Empieza Simulacion una vez que consegui los pids
			semEmpezarSimulacion.v();
			semEmpezarSimulacion.v();
			semEmpezarSimulacion.v();
			semEmpezarSimulacion.v();
			semEmpezarSimulacion.v();
			semEmpezarSimulacion.v();
			semEmpezarSimulacion.v();

			sleep(tiempoEspera);
			cout
					<< "Termino simulacion !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
					<< endl;
			kill(entrada1, SIGINT);
			kill(entrada2, SIGINT);
			kill(entrada3, SIGINT);
			semEscrituraEntrada.v();
			kill(simulacion, SIGINT);
			semLecturaEntrada.v();
			semLecturaEntrada.v();
			semLecturaEntrada.v();

			int stat;
			waitpid(procId, &stat, WUNTRACED);
			kill(salida1, SIGINT);
			kill(salida2, SIGINT);
			semLecturaSalida.v();
			semLecturaSalida.v();
			semEmpezarSimulacion.eliminar();
			semEscrituraSalida.eliminar();
			semLecturaSalida.eliminar();
			semEscrituraEntrada.eliminar();
			semLecturaEntrada.eliminar();
			semTerminarSimulacion.eliminar();
			salida.cerrar();
			salida.eliminar();
			estacionamiento.getAdministrador().terminar();
			semProcIdEscritos.eliminar();
			semEscribirProcId.eliminar();
			procIds.liberar();
			terminoSimulacion.liberar();
		}
	}

	return 0;
}

void crearEntrada(int numero, Semaforo * lecturaEntrada,
		Semaforo* escrituraEntrada, Semaforo* terminar,
		Estacionamiento * estacionamiento) {
	Entrada entradaAuto(numero, lecturaEntrada, escrituraEntrada, terminar);
	entradaAuto.iniciar(estacionamiento);
}

//Pantalla administrador para consultar Plata , Cantidad de autos y Terminar el Programa.
void Pantalla(Estacionamiento& estacionamiento, LockFile& lockTerminoSimulacion,
		MemoriaCompartida<bool>& terminoSimulacion) {
	int opcion = 0;
	bool terminado = false;
	SimulacionHandler admSim;
	SignalHandler::getInstance()->registrarHandler(SIGINT, &admSim);
	while (!terminado) {
		cout << "Elija una opcion" << endl;
		cout << "1 - Plata" << endl;
		cout << "2 - Autos" << endl;
		cout << "3 - Terminar" << endl;
		cin >> opcion;
		switch (opcion) {
		case 1: {
			cout << "1 - Plata" << endl;
			cout
					<< estacionamiento.getAdministrador().consultarDineroRecaudado()
					<< endl;
			break;
		}
		case 2: {
			cout << "2 - Autos" << endl;
			cout << estacionamiento.getAdministrador().consultarCantidadAutos()
					<< endl;
			break;
		}
		case 3: {
			int cantAutos =
					estacionamiento.getAdministrador().consultarCantidadAutos();
			lockTerminoSimulacion.tomarLock(0);
			bool terminoSim = *(terminoSimulacion.leer());
			lockTerminoSimulacion.liberarLock(0);
			if (!terminoSim) {
				cout << "Todavia no termino la simulacion" << endl;
			} else if (cantAutos != 0) {
				cout << "Hay " << cantAutos
						<< " que aun no salieron del estacionamiento. Aguarde por favor."
						<< endl;
			} else {
				terminado = true;
			}

			break;
		}
		default:
			break;
		}

	}

}

void crearProcesoAdministrador(Semaforo& semEmpezarSimulacion,
		Semaforo& semEscrituraEntrada, Semaforo& semLecturaEntrada,
		Estacionamiento& estacionamiento, Semaforo& semEscrituraSalida,
		Semaforo& semLecturaSalida, LockFile & lockTerminoSimulacion,
		MemoriaCompartida<bool> & terminoSimulacion) {
	cout << "Inicia simulacion" << endl;
	semEmpezarSimulacion.p();
	Pantalla(estacionamiento, lockTerminoSimulacion, terminoSimulacion);
}
void crearSalida(int numero, Semaforo * lecturaSalida,
		Semaforo* escrituraSalida) {
	Salida salida(numero, lecturaSalida, escrituraSalida);
	salida.iniciar();
}

void crearEntradasYSalidas(Semaforo& semEmpezarSimulacion,
		Semaforo& semLecturaEntrada, Semaforo& semEscrituraEntrada,
		Semaforo& semTerminarSimulacion, Estacionamiento * estacionamiento,
		Semaforo& semLecturaSalida, Semaforo& semEscrituraSalida,
		Semaforo& semProcIdEscritos, Semaforo& semEscribirProcId,
		MemoriaCompartida<int> * procIdsMemory) {

	pid_t procId = fork();
	if (!procId) {
		procId = fork();
		if (!procId) {
			procId = fork();
			if (!procId) {
				procId = fork();
				if (!procId) {
					//	cout << "Salida 1 pid : " << getpid() << endl;
					semEscribirProcId.p();
					int pid = getpid();
					procIdsMemory->escribir(5, pid);
					semEscribirProcId.v();
					semProcIdEscritos.v();
					semEmpezarSimulacion.p();
					crearSalida(1, &semLecturaSalida, &semEscrituraSalida);
					estacionamiento->liberarMemoria();
					procIdsMemory->liberar();
				} else {
					//	cout << "Salida 2 pid : " << getpid() << endl;
					semEscribirProcId.p();
					int pid = getpid();
					procIdsMemory->escribir(4, pid);
					semEscribirProcId.v();
					semProcIdEscritos.v();
					semEmpezarSimulacion.p();
					crearSalida(2, &semLecturaSalida, &semEscrituraSalida);
					estacionamiento->liberarMemoria();
					procIdsMemory->liberar();
				}
			} else {
				//	cout << "Entrada " << 3 << " con pid : " << getpid() << endl;
				semEscribirProcId.p();
				int pid = getpid();
				procIdsMemory->escribir(2, pid);
				semEscribirProcId.v();
				semProcIdEscritos.v();
				semEmpezarSimulacion.p();
				crearEntrada(3, &semLecturaEntrada, &semEscrituraEntrada,
						&semTerminarSimulacion, estacionamiento);
				estacionamiento->liberarMemoria();
				procIdsMemory->liberar();
			}
		} else {
			//cout << "Entrada " << 2 << " con pid : " << getpid() << endl;
			semEscribirProcId.p();
			int pid = getpid();
			procIdsMemory->escribir(1, pid);
			semEscribirProcId.v();
			semProcIdEscritos.v();
			semEmpezarSimulacion.p();
			crearEntrada(2, &semLecturaEntrada, &semEscrituraEntrada,
					&semTerminarSimulacion, estacionamiento);
			estacionamiento->liberarMemoria();
			procIdsMemory->liberar();
		}
	} else {
		//	cout << "Entrada " << 1 << " con pid : " << getpid() << endl;
		semEscribirProcId.p();
		int pid = getpid();
		procIdsMemory->escribir(0, pid);
		semEscribirProcId.v();
		semProcIdEscritos.v();
		semEmpezarSimulacion.p();
		crearEntrada(1, &semLecturaEntrada, &semEscrituraEntrada,
				&semTerminarSimulacion, estacionamiento);
		estacionamiento->liberarMemoria();
		procIdsMemory->liberar();
	}
}

